﻿//-----------------------------------------------------------------------------------------------------------
// WBFSSync Project by Omega Frost 
// http://wbfssync.codeplex.com/
//
// WBFSSync is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//-----------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WBFSe3.IO;
using WBFSe3.Wbfs;
using System.IO;
using WBFSe3.Interfaces;
using WBFSe3.Streams;

namespace WBFSe3.Formats
{
    //-------------------------------------------------------------------------------------------------------
    //
    //-------------------------------------------------------------------------------------------------------
    public class WbfsFWbfsCreator : WbfsFormatCreator
    {
        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        WbfsDriveInfo drive;
        WbfsPath path;
        WbfsUsage usage;

        WbfsMultiFileStream baseStream;
        WbfsSectorStream sectorStream;

        int numFiles;
        int numSectors;
        int sectorSize;
        int sectorsPerFile;

        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override WbfsPath Path { get { return this.path; } }
        public override int SectorSize { get { return this.sectorSize; } }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override WbfsError Initialize(WbfsDriveInfo drive, String path, String filename,
            IWbfsGameInfo info, WbfsUsage usageTable, int sectorSize, int splitCount)
        {
            this.drive = drive;
            this.sectorSize = sectorSize;
            this.numSectors = usageTable.CountOnes() + 1;

            if (drive.LargeFiles)
            {
                this.numFiles = 1;
                this.sectorsPerFile = numSectors;
            }
            else
            {
                this.sectorsPerFile = (int)(((1L << 32) - 1) / sectorSize);
                this.numFiles = usage.CountOnes() / this.sectorsPerFile;
            }

            String[] files = new String[this.numFiles];
            files[0] = filename;

            for (int i = 1; i < this.numFiles; i++)
                files[i] = filename.Remove(filename.Length - 1) + i;

            this.path = new WbfsPath(path, files);
            this.usage = usageTable;

            return WbfsError.Ok;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override long EstimateFileSize()
        {
            return (long)this.sectorSize * (this.numSectors + 1);
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override WbfsError BeginCreate(out WbfsStream stream)
        {
            long[] layout;

            if (drive.LargeFiles || (this.numFiles == 1))
            {
                layout = new long[] { (long)this.sectorsPerFile * this.sectorSize };
            }
            else
            {
                layout = new long[this.numFiles];

                for (int i = 0; i < this.numFiles - 1; i++)
                    layout[i] = (long)this.sectorsPerFile * this.sectorSize;

                layout[this.numFiles - 1] = (long)(this.numSectors - 
                    (this.sectorsPerFile * this.numFiles)) * this.sectorSize;
            }

            ushort j = 1;
            ushort[] sectors = new ushort[this.usage.Count];
            for (int i = 0; i < this.usage.Count; i++)
            {
                if (this.usage[i])
                    sectors[i] = j++;
            }

            this.baseStream = new WbfsMultiFileStream(this.path, layout, this.sectorSize, FileMode.Create);
            this.sectorStream = new WbfsSectorStream(this.baseStream, 
                WbfsConstants.WbfsDefaultSectorSize, sectors);

            stream = this.sectorStream;

            return WbfsError.Ok;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override WbfsError EndCreate(bool succeeded)
        {
            if (!succeeded)
            {
                this.baseStream.Close();

                foreach (WbfsFileStream stream in this.baseStream.Streams)
                    File.Delete(stream.Name);
            }
            else
            {
                Byte[] filesystem = new byte[this.sectorSize];

                // Calculate some things...

                int wbfsSecsPerDisc = WbfsHelper.WbfsSecsLshPerDisc(
                    WbfsConstants.WbfsDefaultSectorSizeLsh);

                int nbytes = WbfsHelper.FitBit(this.numSectors, 8);

                int sectorTablePos = WbfsHelper.Align(
                    WbfsConstants.WbfsDefaultSectorSize - nbytes, 512);

                int sectorTableSize = WbfsHelper.Fit(nbytes, 512);

                int  discDescriptorSize = WbfsHelper.Fit(WbfsConstants.WbfsDiscHCopySz +
                    wbfsSecsPerDisc * sizeof(ushort), 512);

                int maxDiscs = Math.Min((sectorTablePos - 512) / discDescriptorSize, 512 - 12);


                // Build the header

                Byte[] b = BitConverter.GetBytes(WbfsHelper.htoni(WbfsConstants.WbfsMagic));
                b.CopyTo(filesystem, WbfsConstants.WbfsHeadMagicPos);

                b = BitConverter.GetBytes(WbfsHelper.htoni((int)(this.baseStream.Length / 512)));
                b.CopyTo(filesystem, WbfsConstants.WbfsHeadNSecsPos);

                filesystem[WbfsConstants.WbfsHeadHdSecSzPos] = (byte)9;
                filesystem[WbfsConstants.WbfsHeadWbfsSecSzPos] = WbfsConstants.WbfsDefaultSectorSizeLsh;
                filesystem[WbfsConstants.WbfsHeadWbfsSecSzPos + 1] = 0;
                filesystem[WbfsConstants.WbfsHeadWbfsSecSzPos + 2] = 0;


                // Build the disc table

                for (int i = 1; i < maxDiscs; i++)
                    filesystem[WbfsConstants.WbfsHeadDiscTablePos + i] = 0;
                filesystem[WbfsConstants.WbfsHeadDiscTablePos] = 1;


                // Build the sector table

                for (int i = 0; i < sectorTableSize; i++)
                    filesystem[sectorTablePos + i] = 0;


                // Create the disc descriptor

                this.baseStream.Seek(this.sectorSize, SeekOrigin.Begin);
                this.baseStream.Read(filesystem, 512, WbfsConstants.WbfsDiscHCopySz);

                ushort j = 1;
                for (int i = 0; i < wbfsSecsPerDisc; i++)
                {
                    if (this.usage[i])
                    {
                        BitConverter.GetBytes(WbfsHelper.htons(j++)).CopyTo(
                            filesystem, 512 + WbfsConstants.WbfsDiscHCopySz + 2 * i);
                    }
                    else
                    {
                        filesystem[512 + WbfsConstants.WbfsDiscHCopySz + 2 * i] = 0;
                        filesystem[512 + WbfsConstants.WbfsDiscHCopySz + 2 * i + 1] = 0;
                    }
                }

                // 
                this.baseStream.Streams[0].Position = 0;
                this.baseStream.Streams[0].Write(filesystem, 0, this.sectorSize);
                this.baseStream.Close();
            }

            return WbfsError.Ok;
        }
    }
}
